package com.googlecode.cswish.struts;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Array;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import ognl.NullHandler;

import org.apache.log4j.Logger;

import com.googlecode.cswish.model.PropertyInfo;
import com.googlecode.cswish.struts.spring.BeanIntrospector;
import com.googlecode.cswish.struts.spring.ObjectFactory;
import com.googlecode.cswish.util.EventArrayList;
import com.opensymphony.xwork2.inject.Inject;
import com.opensymphony.xwork2.util.reflection.ReflectionContextState;
import com.opensymphony.xwork2.util.reflection.ReflectionProvider;

/**
 * Adjust the creation logic
 * 
 * @see com.opensymphony.xwork2.conversion.impl.InstantiatingNullHandler
 * @author jerry.feng Date: 2008-9-25
 */
public class InstantiatingNullHandler implements NullHandler {

	/**
	 * @deprecated Use {@link ReflectionContextState#CREATE_NULL_OBJECTS}
	 *             instead
	 */
	public static final String CREATE_NULL_OBJECTS = ReflectionContextState.CREATE_NULL_OBJECTS;
	public static final String ARRAY_OBJECTS_LENGTH = "cswish.NullHandler.arrayLength";
	private final static Logger logger = Logger.getLogger(InstantiatingNullHandler.class);

	private ReflectionProvider reflectionProvider;

	private ObjectFactory objectFactory;

	private BeanIntrospector beanIntrospector;

	@Inject
	public void setObjectFactory(com.opensymphony.xwork2.ObjectFactory objectFactory) {
		this.objectFactory = (ObjectFactory) objectFactory;
		
		beanIntrospector = this.objectFactory.getBean(BeanIntrospector.class, false);
	}

	@Inject
	public void setReflectionProvider(ReflectionProvider prov) {
		this.reflectionProvider = prov;
	}

	public Object nullMethodResult(Map context, Object target, String methodName, Object[] args) {
		if (logger.isDebugEnabled()) {
			logger.debug("Entering nullMethodResult ");
		}

		return null;
	}

	@SuppressWarnings("unchecked")
	public Object nullPropertyValue(Map context, Object target, Object property) {
		if (logger.isDebugEnabled()) {
			logger.debug("Entering nullPropertyValue [target=" + target + ", property=" + property + "]");
		}

		boolean c = ReflectionContextState.isCreatingNullObjects(context);

		if (!c) {
			return null;
		}

		if ((target == null) || (property == null)) {
			return null;
		}

		try {
			String propName = property.toString();
			Object realTarget = reflectionProvider.getRealTarget(propName, context, target);
			Class clazz = null;

			boolean isArray = false;
			if (realTarget != null) {
				isArray = realTarget.getClass().isArray();
				if (!isArray) {
					PropertyDescriptor pd = reflectionProvider.getPropertyDescriptor(realTarget.getClass(), propName);
					if (pd == null) {
						return null;
					}

					clazz = pd.getPropertyType();
				} else {
					clazz = realTarget.getClass().getComponentType();
				}
			}

			if (clazz == null) {
				// can't do much here!
				return null;
			}

			Object param = createObject(clazz, realTarget, propName, context);
			if (!isArray) {
				reflectionProvider.setValue(propName, context, realTarget, param);
			} else {
				// FIXME: Out of range
				((Object[]) realTarget)[new Integer(propName)] = param;
			}
			return param;
		} catch (Exception e) {
			logger.error("Could not create and/or set value back on to object", e);
		}

		return null;
	}

	@SuppressWarnings("unchecked")
	private Object createObject(Class<?> clazz, Object target, String propName, Map context) throws Exception {
		if (Collection.class.isAssignableFrom(clazz)) {
			return new EventArrayList();
		} else if (clazz == Map.class) {
			return new HashMap();
		} else if (clazz.isArray()) {
			Object length = context.get(ARRAY_OBJECTS_LENGTH);
			if (length == null) {
				length = 20;
			}
			return Array.newInstance(clazz.getComponentType(), new Integer(length.toString()));
		}

		PropertyInfo propertyInfo = beanIntrospector.getModelInfo(target.getClass().getName()).getProperty(propName);
		String genericTypeName = propertyInfo == null ? null : propertyInfo.getGenericType();
		return objectFactory.buildBean(genericTypeName, clazz, context);
	}
}